/*
 * Phantasia - Final Fantasy VIII Romhacking Tools
 * Copyright (C) 2005 Ricardo J. Ricken (Darkl0rd)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef LZSENCODER_HPP
#define LZSENCODER_HPP

#include "common.hpp"

class LZSEncoder
{
public:
   LZSEncoder (const u8 *data, int len) :
      encBuffer(0), encLen(0), rawData(data), rawDataLen(len) { }

   ~LZSEncoder ()
   {
      if (encBuffer)
         delete [] encBuffer;
   }

   int getEncodedLen();
   const u8 *getEncodedData ();

   /**
   * Encodes the given byte stream into an internal buffer.
   * @return True on success, false otherwise.
   */
   bool encode ()
   {
      if (encBuffer != 0)
         return false;

      encBuffer = new u8[rawDataLen];
      int rawPos = 0, &encPos = encLen;

      while (rawPos < rawDataLen)
      {
         encBuffer[encPos++] = 0xFF;
         u8 &controlByte = encBuffer[encPos - 1];

         // parse in 8-bytes long blocks
         for (int i=0; i<8; i++)
         {
            // compute sliding window range
            int winRange = rawPos > LZ_WINSIZE ? rawPos - LZ_WINSIZE : 0;
            LZMatch bestMatch;

            // need to cover this:
            /*
            Secondly, you can have a repeated run. This is almost the opposite problem: when you go off the end of
            your output. Say you're at offset 100 in your output, and you have to go to offset 95 to read in a reference.
            This is okay, but if the reference length is >5, you loop the output. So if you had to write out 15 bytes,
            you'd write out the five bytes that were available, then write them out again, then again, to make up the
            15 bytes you needed. The FF7 (and 8!) files use both of these 'tricks', so you can't ignore them. 
            */

            // scan the entire buffer for matches (useful repeated data)
            for (int j=winRange; j < (rawPos > 0 ? rawPos - 1 : rawPos); j++)
               // new!
               if (rawData[j] == 0x00)
               {
                  int k = 0;
                  while((rawData[j + k] == 0x00) && (k < LZ_MAXSIZE)) k++;

                  if (k > 2 && bestMatch.size < k)
                     bestMatch = LZMatch(k, -k);
               }
               else
               // end-new
               if (rawData[j] == rawData[rawPos])
               {
                  int k = 0;
                  while ((rawData[j + k] == rawData[rawPos + k]) && (k < LZ_MAXSIZE)) k++;

                  // keeps track of the best match found so far
                  if (k > 2 && bestMatch.size < k)
                        bestMatch = LZMatch(k, j);
               }

            // prevents overrun in the buffer
            if (rawPos > rawDataLen) break;

            // there is a match
            if (bestMatch.size)
            {
               u16 *pair = (u16 *)&encBuffer[encPos];
               u16 offset = (bestMatch.pos - 18) & LZ_WINSIZE;

               *pair = (bestMatch.size - 3) & 0xf;
               *pair |= ((offset & 0xff) << 8) | (offset & 0xf00) >> 4;
               *pair = (*pair >> 8) | ((*pair << 8) & 0xff00);

               encPos += 2;
               rawPos += bestMatch.size;
               controlByte ^= 0x01 << i;

               bestMatch.size = 0;
            }
            else
               encBuffer[encPos++] = rawData[rawPos++];
         }
      }

      return true;
   }


private:
   struct LZMatch
   {
      LZMatch (int mSize = 0, int mPos = 0) : size(mSize), pos(mPos) { }

      int size;
      int pos;
   };

   enum
   {
      LZ_MAXSIZE = 0x12,  /**< 4-bit size provides an 18-byte match */
      LZ_WINSIZE = 0x0fff /**< sliding window represented by a 4096-bytes circular buffer */
   };

   u8 *encBuffer;
   int encLen;

   const u8 *rawData;
   int rawDataLen;
};

#endif //~LZSENCODER_HPP